package com.chenu.gamscriptman.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.chenu.gamscriptman.compoent.Global;
import com.chenu.gamscriptman.dao.*;
import com.chenu.gamscriptman.domain.*;
import com.chenu.gamscriptman.domain.noindb.*;
import com.chenu.gamscriptman.service.DeviceTask;
import com.chenu.gamscriptman.service.NotifyService;
import com.chenu.gamscriptman.service.UserDataTask;
import com.chenu.gamscriptman.utils.ExcelUtils;
import com.chenu.gamscriptman.utils.TimeUtils;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;

import java.util.*;

import static com.chenu.gamscriptman.domain.Device.groupNrs;
import static com.chenu.gamscriptman.domain.Device.noGroupNr;

/**
 * @author flyan
 * date 2019-12-29
 * @function 用户数据任务
 */
@CacheConfig(cacheNames = "userdata")   /* 本类使用缓存名称：userdata */
@Service("userDataTask")
public class UserDataTaskImpl implements UserDataTask {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /* 请求用户数据服务的服务路由键 */
    public static final String SERVICE_ROUTING_KEY = "userdata";

    /* 消息服务器的用户数据消息队列 */
    public static final String QUEUE = "gamscriptman.userdatatask.queue";

    /* 数据库数据是否被更新了 */
    public static boolean DbIsUpdate = true;

    /* redis模板，用于缓存和处理结果的暂存 */
    @Autowired
    private RedisTemplate jsonRedisTemplate;

    /* gson，用于处理json数据 */
    @Autowired
    private Gson gson;
    private Gson userDataGson = new GsonBuilder()

            .setExclusionStrategies(new ExclusionStrategy() {

                /* 设置需要忽略的字段 */
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return UserData.ignoreAttr.contains(f.getName());
                }

                /* 设置需要清空的字段类型 */
                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            }).create();

    /* 其他任务 */
    @Autowired
    NotifyService notifyService;
    @Autowired
    DeviceTask deviceTask;

    /* 事务管理器 */
    @Autowired
    DataSourceTransactionManager transactionManager;

    /* dao */
    @Autowired
    UserDataDao userDataDao;
    @Autowired
    UserDao userDao;
    @Autowired
    GameTaskDao gameTaskDao;
    @Autowired
    GameDao gameDao;
    @Autowired
    GameDataDao gameDataDao;
    @Autowired
    DeviceDao deviceDao;

    /* 任务能提供的所有功能索引号 */
    public static final int USERDATA_LIST = 0;                /* 得到所有用户数据信息 */
    public static final int USERDATA_ADD_OR_EDIT_VIEW = 1;    /* 前往用户数据添加/编辑页面 */
    public static final int USERDATA_ADD_OR_UPDATE = 2;       /* 用户数据添加或更新 */
    public static final int USERDATA_REMOVE = 3;              /* 用户数据删除 */
    public static final int USERDATA_COUNT = 4;               /* 得到用户数据数量 */
    public static final int USERDATA_GET = 5;                 /* 得到一条用户数据信息，可通过用户数据id/设备号/MAC地址 */
    public static final int USERDATA_EXPORT_FILE = 6;           /* 导出结果为文件 */

    /* 任务入口，等待一条消息并处理，然后回发一条包含处理结果的消息 */
    @Override
    @RabbitListener(queues = QUEUE)
    public Message task(Message message) {
        Message rs;

        /* 看看用户数据想做什么？ */
        switch (message.getType()){
            case USERDATA_LIST:                 /* 列出所有用户数据 */
                logger.debug("用户数据任务：USERDATA_LIST...");
                rs = do_list(message);
                break;

            case USERDATA_ADD_OR_EDIT_VIEW:     /* 需要前往用户数据添加或编辑页面 */
                logger.debug("用户数据任务：USERDATA_ADD_OR_EDIT_VIEW...");
                rs = do_no_call(message);       /* 用户数据不允许在页面上添加，不提供这个功能 */
                break;

            case USERDATA_ADD_OR_UPDATE:        /* 添加或更新一个用户数据 */
                logger.debug("用户数据任务：USERDATA_ADD_OR_UPDATE...");
                rs = do_add_or_update(message);
                break;

            case USERDATA_REMOVE:               /* 删除一个用户数据 */
                logger.debug("用户数据任务：USERDATA_REMOVE...");
                rs = do_remove(message);
                break;

            case USERDATA_COUNT:               /* 得到用户数据数量 */
                logger.debug("用户数据任务：USERDATA_COUNT...");
                rs = do_count(message);
                break;

            case USERDATA_GET:                    /* 得到一条用户数据信息 */
                logger.debug("用户数据任务：USERDATA_GET...");
                rs = do_get(message);
                break;

            case USERDATA_EXPORT_FILE:
                logger.debug("用户数据任务：USERDATA_EXPORT_FILE...");
                rs = do_export_file(message);
                break;

            default:
                logger.warn("用户数据任务：你正在请求一个不存在的功能服务...");
                rs = do_no_call(message);
                break;
        }

        /* 返回包含处理结果的消息 */
        return rs;
    }

    private Message do_export_file(Message message) {

        /* 得到数据，它们在缓存中！ */
        PageBean<UserData> data = (PageBean<UserData>) jsonRedisTemplate.opsForValue().get(Global.REDIS_USERDATA_PAGES);
        /* 得到数据映射 */
        GameMap map = (GameMap)jsonRedisTemplate.opsForValue().get(Global.REDIS_USERDATA_MAP);

        /* 设置Excel文件标题 */
        List<String> title = new ArrayList<>();
        title.add("#");
        title.add("设备号");
        title.add("大区");
        title.add("服务器");
        title.add("角色编号");
        title.add("运行游戏");
        for(String key : map.getMap().keySet()){
            title.add(map.getMap().get(key).getName());
        }
        title.add("更新时间");
        title.add("管理员");
        /* 设置列顺序 */
        List<String> keyList = new ArrayList<>();
        keyList.add("id");
        keyList.add("gameTask.sdevNr");
        keyList.add("gameTask.region");
        keyList.add("gameTask.server");
        keyList.add("gameTask.role");
        keyList.add("gameName");
        keyList.addAll(map.getMap().keySet());
        keyList.add("uptime");
        keyList.add("admin.username");
        /* 设置内容 */
        List<Map<String, Object>> context = new ArrayList<>();
        Map<String, Object> line;
        for(UserData userData : data.getRecords()){
            line = new HashMap<>();
            line.put("id", userData.getId());
            line.put("gameTask.sdevNr", userData.getGameTask().getSdevNr());
            line.put("gameTask.region", userData.getGameTask().getRegion());
            line.put("gameTask.server", userData.getGameTask().getServer());
            line.put("gameTask.role", userData.getGameTask().getRole());
            line.put("gameName", userData.getGameName());
            for(String key : map.getMap().keySet()){
                line.put(key, userData.getResourceDataMap().get(key));
            }
            line.put("uptime", TimeUtils.unixStamp2Date(userData.getUptime(), "yyyy-MM-dd hh:mm:ss"));
            line.put("admin.username", userData.getAdmin().getUsername());
            context.add(line);
        }

        /* 生成Execl文件 */
        try {
            String excelFilePath = ExcelUtils.createExeclFileByDBList("用户数据", title, keyList, context);
            logger.debug("--> " + excelFilePath + " <--");
            /* 放入文件真实路径 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_DOWNLOAD_FILE_PATH, excelFilePath);
            /* 成功，准备下载文件 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_SUCCESS,
                    "/download"
            );
        } catch (Exception e) {
            e.printStackTrace();
            /* 失败 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_FAILURE,
                    "用户数据任务：生成Excel文件失败，请重试..."
            );
        }
    }

    private Message do_list(Message message) {
        /* 它很简单，根据条件和分页查询用户数据列表 */
        PageBean<UserData> data = null;
        Statistics statistics = null;   /* 统计信息 */
        String rtData = null;

        /* 解析消息中的数据 */
        ListMsgParameter<UserData> parameter = gson.fromJson(message.getData(), new TypeToken<ListMsgParameter<UserData>>() {}.getType());
        if(parameter != null && parameter.getPageBean() != null){    /* 不为null才需要 */
            parameter.setPageBean(new PageBean<UserData>(
                    parameter.getPageBean().getCurrent(),
                    parameter.getPageBean().getSize(),
                    true,
                    parameter.getPageBean().getDynamicHopeSize()
            ));
        }

        /* 获取所有游戏名称列表 */
        List<String> gameNameList = new ArrayList<>();
        for (Game game:
                gameDao.selectList(new QueryWrapper<Game>()
                        .select("name"))) {
            gameNameList.add(game.getName());
        }

        /* 检查查询参数 */
        boolean paging = false;     /* 是否需要分页？ */
        if(gameNameList.size() != 0){
            if(parameter == null) { /* 没有任何限制，查看用户数据分组按游戏，默认游戏名称为第一个 */
                parameter = new ListMsgParameter<UserData>()
                        .setPageBean(null)
                        .setQueryBean(new UserData().setGameName(gameNameList.get(0)));
            } else {
                /* 保证按游戏分组查询，没有就按游戏名称的第一个游戏 */
                if(parameter.getQueryBean() == null){
                    parameter.setQueryBean(new UserData().setGameName(gameNameList.get(0)));
                } else if(StringUtils.isEmpty(parameter.getQueryBean().getGameName())){
                    parameter.setQueryBean(parameter.getQueryBean().setGameName(gameNameList.get(0)));
                }
                paging = true;
            }
        } else {
            /* 还没有任何游戏，那么游戏数据也理应没有 */
            parameter = new ListMsgParameter<UserData>()
                    .setQueryBean(new UserData()
                            .setGameName(""));
        }

        /* 检查是否可以使用更快速的缓存 */
        UserData queryBean = (UserData) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_USERDATA);
        PageBean<UserData> pageBean = (PageBean<UserData>)jsonRedisTemplate.opsForValue().get(Global.REDIS_USERDATA_PAGES);
        if(new ListMsgParameter<UserData>()
                .setEx_int(null)
                .setQueryBean(queryBean)
                .setPageBean(pageBean)
                .equals(parameter) && !DbIsUpdate){
            logger.debug("用户数据列表 --> 使用缓存");
            /* 查询条件一样，而且数据库近期没更新，那么不需要去数据库拿了，缓存中有 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_SUCCESS,
                    "/userdatas"
            );
        }
        logger.debug("用户数据列表 --> 数据库查询");

        /* 查询，这一步只有上面的缓存用不到才会执行到 */
        data = findAll(parameter.getPageBean(), parameter.getQueryBean());
        if(paging) data.calcDynamicBar();  /* 计算动态显示条 */

        /* 现在得到统计信息 */
        statistics = new Statistics().
                setNowTotal(userDataDao.selectCount(null)).
                setNowUpdated(0);       /* 还没完成今日更新统计@TODO */

        if(data.getRecords() == null){       /* 查询失败了 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_FAILURE,
                    "用户数据任务：USERDATA_LIST失败......"
            );
        }

        /* 查询失败了，直接返回错误 */
        if(data.getRecords() == null){
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_FAILURE,
                    "用户数据任务：USERDATA_LIST失败......"
            );
        }

        /* 现在解析映射 */
        Game game = null;
        GameMap gameDataMap = null;
        if(gameNameList.size() != 0){
            game = gameDao.selectOne(new QueryWrapper<Game>()
                    .select("data_map")
                    .eq("name", parameter.getQueryBean().getGameName()));
            gameDataMap = gson.fromJson(game.getDataMap(), GameMap.class);
            jsonRedisTemplate.opsForValue().set(Global.REDIS_USERDATA_MAP, gameDataMap);
            logger.debug("Global.REDIS_GAMEDATA_MAP --> " + gameDataMap);
        }

        /* 成功处理 */
        if(message.isNeedView()){
            /* 如果需要视图，将结果、参数放入缓存，并设置返回的页面 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_USERDATA_PAGES, data);
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_USERDATA, parameter.getQueryBean());
            /* 还有统计信息 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_USERDATA_STATISTICS, statistics);
            /* 以及所有的游戏名 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_GAME_NAME_LIST, gameNameList);
            rtData = "/userdatas";
        } else {
            /* 不需要视图，将结果转化为json串放到回复消息中 */
            rtData = gson.toJson(new ListResult<UserData>().setPageBean(data).setStatistics(statistics));
            logger.warn("--> " + rtData);
        }

        /* 返回结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );
    }

    /**
     * 处理用户数据的添加个更新服务调用
     * 注意：用户数据的添加和更新没有页面
     * @param message
     * @return
     */
    private Message do_add_or_update(Message message) {
        int rows;
        boolean isInsert = false, noId = false;
        int handleState;
        String returnData;
        GameData gameData = null;

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        /* 解析消息中的用户数据 */
        DevNr devNr = new DevNr();
        try {                       /* 解析成功 */
            UserData userData = gson.fromJson(message.getData(), UserData.class);
            if(userData.getId() == null) noId = true;
            if(noId){       /* 未指明ID，现在进行下一步判断 */
                if(userData.getGameTask() == null || userData.getGameTask().getSdevNr() == null ||
                        StringUtils.isEmpty(userData.getGameName())){
                    /* 添加一个用户数据，至少至少要有设备号以及用户数据运行哪个游戏！ */
                    handleState = Message.HANDLER_FAILURE;
                    returnData = "用户数据任务：添加一个用户数据，但没给出其设备号和运行游戏，无法继续...";
                    /* 这里直接返回错误结果 */
                    transactionManager.rollback(transStatus);
                    return MessageServiceImpl.TaskFinish(
                            message,
                            (long) MessageServiceImpl.USERDATA_TASK,
                            handleState,
                            returnData
                    );
                } else {        /* 有设备号，还不够，需要进一步确认 */
                    /* 因为一个游戏任务由[设备号 + 大区 + 服务器 + 角色编号 + 运行游戏]
                     * 唯一标识，所以在这里要做一些很脏很累的活。
                     */
                    QueryWrapper<GameTask> queryWrapper = new QueryWrapper<>();
                    if(userData.getGameTask().getSdevNr() != null){
                        if(deviceTask.getDevice(userData.getGameTask().getSdevNr(), devNr) == null){
                            /* 这个设备不存在，没必要继续了 */
                            handleState = Message.HANDLER_FAILURE;
                            returnData = "用户数据任务：该设备(设备号：" + userData.getGameTask().getSdevNr() + ")不存在，无法继续...";
                            transactionManager.rollback(transStatus);
                            /* 这里直接返回错误结果 */
                            return MessageServiceImpl.TaskFinish(
                                    message,
                                    (long) MessageServiceImpl.USERDATA_TASK,
                                    handleState,
                                    returnData);
                        }
                        queryWrapper.eq("dev_nr", devNr.getDevNr())
                                .eq("group_nr", devNr.getGroupNr());
                    }
                    if(userData.getGameTask().getRegion() != null){
                        queryWrapper.eq("region", userData.getGameTask().getRegion());
                    }
                    if(userData.getGameTask().getServer() != null){
                        queryWrapper.eq("server", userData.getGameTask().getServer());
                    }
                    if(userData.getGameTask().getRole() != null){
                        queryWrapper.eq("role", userData.getGameTask().getRole());
                    }
                    List<GameTask> tasks = gameTaskDao.selectList(queryWrapper);    /* 前四个条件 */

                    /* 现在看看这些游戏任务有没有符合第五个条件，即运行游戏相同的，但在那之前先得知道这个游戏存不存在 */
                    if(gameDao.selectCount(new QueryWrapper<Game>()
                            .eq("name", userData.getGameName())) == 0){
                        /* 不存在，可以结束了 */
                        handleState = Message.HANDLER_FAILURE;
                        returnData = "用户数据任务：指出的游戏(" +  userData.getGameName() + ")不存在，无法进行下一步操作。";
                        transactionManager.rollback(transStatus);
                        return MessageServiceImpl.TaskFinish(
                                message,
                                (long) MessageServiceImpl.USERDATA_TASK,
                                handleState,
                                returnData
                        );
                    }
                    boolean found = false;
                    UserData search = null;
                    for (GameTask gt :
                            tasks) {
                        search = userDataDao.selectById(gt.getUserDataFK());
                        if(userData.getGameName().equals(search.getGameName())){ /* 符合 */
                            /* 得到该条用户数据 */
                            found = true;
                            break;      /* 找到了，结束遍历 */
                        }
                    }

                    /* 检查查询是否成功 */
                    if(!found){
                        /* 数据库确实不存在这条用户数据，认为它是可添加的先，下面还需要继续判断
                         */
                        isInsert = true;
                    } else {
                        /* 找到相同，是更新，放入这条用户数据的Id */
                        userData.setId(search.getId());
                    }

                    logger.debug(isInsert ? "do_add --> " : "do_update --> " + userData);
                }
            }

            /* 执行添加和更新
             * 在那之前设置更新时间为当前时间，不管用户指没指定
             */
            userData.setUptime(TimeUtils.date2UnixStamp(new Date()));
            userData.setAdminFk(message.getSource());       /* 管理员 */
            if(isInsert){
                /* 检查游戏数据中的设备范围，得到该用户数据应该使用的游戏数据
                 * 它的规则是：从最新的游戏数据查找到最旧的游戏数据，如果其中某个设备范围中包含了该设备号，那么
                 * 这条游戏数据就是我们需要的，拿出来；如果查到最后都莫得包含此设备号，那么说明这条用户数据并没有
                 * 得到一个任务，我们拒绝添加。
                 */
                boolean fond = false;       /* 找到没？ */
                List<GameData> gameDataList = gameDataDao.selectList(new QueryWrapper<GameData>()
                        .select("id", "dev_range", "game_fk")
                        .orderByDesc("id"));    /* desc倒序 */
                logger.debug("所有的游戏数据(新到旧排序) --> " + gameDataList);
                for (GameData gd:
                     gameDataList) {
                    if(fond) break;     /* 如果已经找到，停止解析 */
                    gd.setGameName(gameDao.selectOne(new QueryWrapper<Game>()
                            .select("name")
                            .eq("id", gd.getGameFK()))
                            .getName());
                    /* 首先解析用户数据范围 */
                    String[] ranges = gd.getDevRange().split(",");
                    if(ranges.length == 0){ /* 这个用户数据范围是错误不存在的 */
                        handleState = Message.HANDLER_FAILURE;
                        returnData = "用户数据任务：游戏数据中存在不正确的设备范围(" + gd.getDevRange()+ ")，无法添加用户数据，请先修复该条游戏数据。";
                        transactionManager.rollback(transStatus);
                        return MessageServiceImpl.TaskFinish(
                                message,
                                (long) MessageServiceImpl.USERDATA_TASK,
                                handleState,
                                returnData
                        );
                    } else {                /* 游戏范围正常 */
                        /* 现在判断添加的用户数据的用户数据存在该用户数据范围中吗？ */
                        for (String range: ranges) {
                            String[] mulOrSingleRange = range.split("-");
                            boolean isMul;
                            if(mulOrSingleRange.length == 2){
                                isMul = true;
                            } else if (mulOrSingleRange.length == 1){
                                isMul = false;
                            } else {
                                handleState = Message.HANDLER_FAILURE;
                                returnData = "用户数据任务：游戏数据中有设备范围(" + gd.getDevRange()+ ")不正确，无法添加用户数据，请先修复该条游戏数据。";
                                transactionManager.rollback(transStatus);
                                return MessageServiceImpl.TaskFinish(
                                        message,
                                        (long) MessageServiceImpl.USERDATA_TASK,
                                        handleState,
                                        returnData
                                );
                            }
                            if(isMul) {      /* 多范围，即 A-B 这样的格式 */logger.debug("--> Mul");
                                try {
                                    /* 先算出范围前后的设备号 */
                                    DevNr p1DevNr = new DevNr();
                                    deviceTask.getDevice(mulOrSingleRange[0], p1DevNr);
                                    DevNr p2DevNr = new DevNr();
                                    deviceTask.getDevice(mulOrSingleRange[1], p2DevNr);
                                    /* 要么像 P1-P2 这样都分组，要么像 1-2 这样都不分组，不允许这样 P1-2 一边分组另一边不分组 */
                                    if(!p1DevNr.getGroupNr().equals(p2DevNr.getGroupNr())){
                                        handleState = Message.HANDLER_FAILURE;
                                        returnData = "游戏数据任务：游戏数据中存在游戏数据范围[" + gd.getDevRange() + "]不正确，无法添加游戏数据，" +
                                                "范围要么都分组，要么都不分组，不允许出现一边分组另一边不分组的范围！";
                                        return MessageServiceImpl.TaskFinish(
                                                message,
                                                (long) MessageServiceImpl.GAMEDATA_TASK,
                                                handleState,
                                                returnData
                                        );
                                    }
                                    Long devNrStart = p1DevNr.getDevNr();
                                    Long devNrEnd = p2DevNr.getDevNr();
                                    logger.debug(" --> " + devNrStart + " | " + devNrEnd + "<-- ");
                                    for (long i = devNrStart; i <= devNrEnd; i++){
                                        logger.debug(" --> " + i);
                                        /* 好的，找到这台设备应该使用的游戏数据了！同时这个游戏数据
                                         * 指出的游戏也需要和用户给出的游戏相同
                                         */
                                        if(p1DevNr.getGroupNr().equals(Device.noGroupNr)){  /* 不需要分组 */
                                            if(devNr.getDevNr().equals(i) && gd.getGameName().equals(userData.getGameName())){
                                                fond = true;
                                                gameData = gd;
                                                break;                                              /* 找到了，没必要停止再往下查找了 */
                                            }
                                        } else {    /* 需要分组 */
                                            if(devNr.getDevNr().equals(i) && devNr.getGroupNr().equals(p1DevNr.getGroupNr()) &&
                                                    gd.getGameName().equals(userData.getGameName())){
                                                fond = true;
                                                gameData = gd;
                                                break;                                              /* 找到了，没必要停止再往下查找了 */
                                            }
                                        }
                                    }
                                } catch (Exception e){
                                    handleState = Message.HANDLER_FAILURE;
                                    returnData = "用户数据任务：游戏数据中有设备范围(" + gd.getDevRange()+ ")不正确，无法添加用户数据，请先修复该条游戏数据。";
                                    transactionManager.rollback(transStatus);
                                    return MessageServiceImpl.TaskFinish(
                                            message,
                                            (long) MessageServiceImpl.USERDATA_TASK,
                                            handleState,
                                            returnData
                                    );
                                }
                            } else {            /* 单范围，即只指出单个用户数据 */
                                DevNr pDevNr = new DevNr();
                                deviceTask.getDevice(mulOrSingleRange[0], pDevNr);
                                if(pDevNr.getGroupNr().equals(Device.noGroupNr)){   /* 不需要分组 */
                                    if(devNr.getDevNr().equals(pDevNr.getDevNr()) && gd.getGameName().equals(userData.getGameName())){
                                        fond = true;
                                        gameData = gd;
                                        break;                                              /* 找到了，没必要停止再往下查找了 */
                                    }
                                } else {    /* 需要分组 */
                                    if(devNr.getDevNr().equals(pDevNr.getDevNr()) && devNr.getGroupNr().equals(pDevNr.getGroupNr()) &&
                                            gd.getGameName().equals(userData.getGameName())){
                                        fond = true;
                                        gameData = gd;
                                        break;                                              /* 找到了，没必要停止再往下查找了 */
                                    }
                                }
                            }
                        }
                    }
                }
                if(fond){       /* 找到 */
                    rows = save(userData);
                    /* 创建游戏任务*/
                    GameTask gameTask = userData.getGameTask();
                    gameTask.setGroupNr(devNr.getGroupNr());
                    gameTask.setDevNr(devNr.getDevNr());
                    gameTask.setGameDataFK(gameData.getId());           /* 设置任务的游戏数据 */
                    gameTask.setUserDataFK(userDataDao.selectNewId());  /* 设置任务的用户数据 */
                    gameTaskDao.insert(gameTask);
                    /* 这一步很重要，设备将会被调度，更新该设备的运行游戏名称 */
                    deviceDao.update(new Device().setRunGame(userData.getGameName()),
                            new UpdateWrapper<Device>()
                                    .eq("group_nr", gameTask.getGroupNr())
                                    .eq("dev_nr", gameTask.getDevNr()));
                } else {        /* 没找到 */
                    rows = 0;
                }
            } else {
                rows = update(userData);
            }

            if(rows > 0){   /* 处理成功 */
                handleState = Message.HANDLER_SUCCESS;
                returnData = isInsert ? "用户数据任务：添加用户数据成功！" : "用户数据任务：更新用户数据成功！";
            } else {        /* 处理失败 */
                handleState = Message.HANDLER_FAILURE;
                returnData = isInsert ? "用户数据任务：添加用户数据失败，可能原因是指定设备不存在或者该用户数据还没有被任何游戏数据调度，请检查重试..."
                        : "用户数据任务：更新用户数据失败，可能原因是指定设备不存在或者该用户数据还没有被任何游戏数据调度，请检查重试...";
            }

            /* 提交事务 */
            transactionManager.commit(transStatus);

        } catch (Exception e){      /* 解析失败 */
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            handleState = Message.HANDLER_FAILURE;
            returnData = "用户数据任务：添加或更新的用户数据不正确，请检查...";
        }

        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                handleState,
                returnData
        );
    }

    private Message do_remove(Message message) {
        PageBean<UserData> data = null;
        int rows = 0;

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        try {
            Long id = Long.valueOf(message.getData());
            /* 在删除用户数据前，必须先删除产生它的游戏任务 */
            gameTaskDao.delete(new UpdateWrapper<GameTask>().eq("user_data_fk", id));
            rows = remove(id);
            if(rows > 0){   /* 删除成功 */
                /* 给用户一条通知提示成功提示信息 */
                notifyService.notify(message.getSource(), "用户数据（" + id + "）删除成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<UserData> pageBean = (PageBean<UserData>) jsonRedisTemplate.opsForValue().get(Global.REDIS_USERDATA_PAGES);
                UserData queryBean = (UserData) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_USERDATA);
                ListMsgParameter<UserData> parameter = new ListMsgParameter();
                parameter.setPageBean(pageBean).setQueryBean(queryBean);
                message.setData(gson.toJson(parameter));
                /* 提交事务 */
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {
                transactionManager.rollback(transStatus);
                return MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.USERDATA_TASK,
                        Message.HANDLER_FAILURE,
                        "用户数据任务：删除失败，这个用户数据可能不存在或已经被移除，请重试..."
                );
            }
        } catch (Exception e){
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.USERDATA_TASK,
                    Message.HANDLER_FAILURE,
                    "用户数据任务：删除失败，你未指明任何设备号"
            );
        }
    }

    private Message do_count(Message message) {
        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                Message.HANDLER_SUCCESS,
                String.valueOf(userDataDao.selectCount(null))
        );
    }

    private Message do_get(Message message) {
        int handleState;
        String returnData;
        UserData userData = null;

        /* 解析消息中的用户数据数据 */
        DevNr devNr = new DevNr();
        try {
            UserData queryBean = gson.fromJson(message.getData(), UserData.class);
            logger.debug("获取用户数据，条件 --> " + queryBean);
            /* 检查查询条件：[设备号 + 大区 + 服务器 + 角色编号 + 运行游戏]标识一个唯一的用户数据 */
            QueryWrapper<GameTask> queryWrapper = new QueryWrapper<>();
            if(queryBean.getGameTask().getSdevNr() != null){
                deviceTask.getDevice(queryBean.getGameTask().getSdevNr(), devNr);
                queryWrapper.eq("dev_nr", devNr.getDevNr())
                        .eq("group_nr", devNr.getGroupNr());
            }
            if(queryBean.getGameTask().getRegion() != null){
                queryWrapper.eq("region", queryBean.getGameTask().getRegion());
            }
            if(queryBean.getGameTask().getServer() != null){
                queryWrapper.eq("server", queryBean.getGameTask().getServer());
            }
            if(queryBean.getGameTask().getRole() != null){
                queryWrapper.eq("role", queryBean.getGameTask().getRole());
            }
            List<GameTask> gameTaskList = gameTaskDao.selectList(queryWrapper);    /* 先查出符合前四个条件的所有游戏任务 */
            /* 现在看看这些游戏任务有没有符合第五个条件，即运行游戏相同的 */
            boolean found = false;
            for (GameTask gt :
                    gameTaskList) {
                userData = userDataDao.selectById(gt.getUserDataFK());
                if(queryBean.getGameName().equals(userData.getGameName())){ /* 符合 */
                    /* 得到该条用户数据 */
                    found = true;
                    break;      /* 找到了，结束遍历 */
                }
            }

            /* 检查查询是否成功 */
            if(found){
                userData.setAdminUsername(userDao.selectUsernameById(userData.getAdminFk()));   /* 管理员用户名 */
                handleState = Message.HANDLER_SUCCESS;
                returnData = userDataGson.toJson(userData);
                logger.debug("--> " + returnData);
            } else {
                handleState = Message.HANDLER_FAILURE;
                returnData = "用户数据任务：没有找到这条用户数据" +
                        "[设备号:" + queryBean.getGameTask().getSdevNr() + ", 大区:" + queryBean.getGameTask().getRegion() +
                        ", 服务器:" + queryBean.getGameTask().getServer() + ", 角色编号:" + queryBean.getGameTask().getRole() +
                        ", 运行游戏:" + queryBean.getGameName() + "]" +
                        "的信息，请先通过/userdata/save_or_update接口上传...";
            }
        } catch (Exception e){  /* 解析失败 */
            handleState = Message.HANDLER_FAILURE;
            returnData = "用户数据任务：给出的查询条件错误，要得到一条用户数据，通过指定：[设备号 + 大区 + 服务器 + 角色编号 + 运行游戏]" +
                    "，如果其中有不需要的条件（设备号和运行游戏必须指定），直接不给令其为空即可。";
        }

        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                handleState,
                returnData
        );
    }

    private Message do_no_call(Message message) {
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                Message.HANDLER_FAILURE,
                "用户数据任务：你请求了一个不存在的功能服务......"
        );
    }

    /**
     * 这个方法会占用很多的系统资源，因为用户数据支持自定义的字段映射，所以条件判断
     * 也需要我们自己完成，不能通过数据库sql语句帮我实现，但是在好处面前，这些坏处
     * 我们可以接受。
     * @param pageBean 分页对象
     * @param queryBean 查询对象
     * @return 包含处理数据结果的分页对象
     */
    @Override
    public PageBean<UserData> findAll(PageBean<UserData> pageBean, UserData queryBean) {
        DbIsUpdate = false;
        /* 分组查询从游戏任务中拿出该游戏的所有用户数据 */
        if(pageBean == null) {
            pageBean = new PageBean<>();
        }
        pageBean.setRecords(userDataDao.selectAllByGameName(pageBean, queryBean.getGameName()));

        int beforeSize = pageBean.getRecords().size();   /* 过滤之前的大小 */

        /* 获得用户数据的任务数据和管理员 */
        for(UserData ud : pageBean.getRecords()){
            ud.setGameTask(gameTaskDao.selectOne(new QueryWrapper<GameTask>().eq("user_data_fk", ud.getId())));
            ud.getGameTask().setSdevNr((ud.getGameTask().getGroupNr().equals(noGroupNr) ? "" :
                    ud.getGameTask().getGroupNr()) + ud.getGameTask().getDevNr());
            ud.setAdmin(new User().setUsername(userDao.selectUsernameById(ud.getAdminFk())));
        }
        
        /* 看看用户需不需要通过设备号过滤数据 */
        if(queryBean.getGameTask() != null && !StringUtils.isEmpty(queryBean.getGameTask().getDevNr())){
            /* 设备号开始 */
            final long devNr = queryBean.getGameTask().getDevNr();
            /* 不符合规则的就删除 */
            pageBean.getRecords().removeIf(userData -> userData.getGameTask().getDevNr() < devNr);
        }
        if(!StringUtils.isEmpty(queryBean.getDevNrEnd())){
            /* 设备号结束 */
            final long devNr = queryBean.getDevNrEnd();
            /* 不符合规则的就删除 */
            pageBean.getRecords().removeIf(userData -> userData.getGameTask().getDevNr() > devNr);
        }

        /* 解析资源数据，后面的条件规则查询需要用到 */
        for (UserData userData :
                pageBean.getRecords()) {
            Map<String, String> map = gson.fromJson(userData.getData(), new TypeToken<Map<String, String>>() {}.getType());
            userData.setResourceDataMap(map);
            logger.debug("--> " + map);
        }

        /* 根据自定义条件规则查询，它很复杂，因为键/值的数据是根据映射不同而不同
         * 所以我们得自己实现解析映射并完成条件的筛选
         */
        if(!StringUtils.isEmpty(queryBean.getRule())){
            queryBean.setRule(queryBean.getRule().replace(" ", ""));
            /* 首先得到有几个条件 */
            String[] conditions = queryBean.getRule().split(",");
            logger.debug("conditions --> " + Arrays.toString(conditions));
            for(String condition : conditions){
                logger.debug("condition --> " + condition);
                List<String> vcv;
                /* 解析条件，这里要注意，两个字符的条件需要先解析（>=、<=），因为如果它们在后面那么>=将会被=解析，这不是我们想看到的 */
                if(condition.contains("like")){
                    logger.debug("condition --> like");
                    vcv = Arrays.asList(condition.split("like"));
                    if(vcv.size() == 2) {
                        /* 模糊查询 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !userData.getResourceDataMap().get(vcv.get(0)).contains(vcv.get(1))
                            ));
                        } catch (Exception e){
                            /* 条件给出的字段不存在 */
                            System.out.println("异常 --> 条件给出的字段不存在");
                        }
                    }
                } else if(condition.contains(">=")){
                    logger.debug("condition --> >=");
                    vcv = Arrays.asList(condition.split(">="));
                    if(vcv.size() == 2) {
                        /* 对于不等式，必须先检查该字段是不是真的是数值型 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !(Long.valueOf(userData.getResourceDataMap().get(vcv.get(0))) >= Long.valueOf(vcv.get(1)))
                            ));
                        } catch (Exception e){
                            /* 出现异常，说明该字段可能不是数值型或给出的条件值不是数值型，还有一种特殊情况，就是条件给出的字段不存在 */
                            System.out.println("异常 --> 该字段可能不是数值型或给出的条件值不是数值型");
                        }
                    }
                } else if(condition.contains("<=")){
                    logger.debug("condition --> <=");
                    vcv = Arrays.asList(condition.split("<="));
                    if(vcv.size() == 2) {
                        /* 对于不等式，必须先检查该字段是不是真的是数值型 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !(Long.valueOf(userData.getResourceDataMap().get(vcv.get(0))) <= Long.valueOf(vcv.get(1)))
                            ));
                        } catch (Exception e){
                            /* 出现异常，说明该字段可能不是数值型或给出的条件值不是数值型，还有一种特殊情况，就是条件给出的字段不存在 */
                            System.out.println("异常 --> 该字段可能不是数值型或给出的条件值不是数值型");
                        }
                    }
                } else if(condition.contains("!=")){
                    logger.debug("condition --> !=");
                    vcv = Arrays.asList(condition.split("!="));
                    if(vcv.size() == 2) {
                        /* 是一个好的条件，不符合规则的就删除 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            userData.getResourceDataMap().get(vcv.get(0)).equals(vcv.get(1))
                            ));
                        } catch (Exception e){
                            /* 条件给出的字段不存在 */
                            System.out.println("异常 --> 条件给出的字段不存在");
                        }
                    }
                } else if(condition.contains("=")){
                    logger.debug("condition --> =");
                    vcv = Arrays.asList(condition.split("="));
                    if(vcv.size() == 2) {
                        /* 是一个好的条件，不符合规则的就删除 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !userData.getResourceDataMap().get(vcv.get(0)).equals(vcv.get(1))
                            ));
                        } catch (Exception e){
                            /* 条件给出的字段不存在 */
                            System.out.println("异常 --> 条件给出的字段不存在");
                        }
                    }
                } else if(condition.contains(">")){
                    logger.debug("condition --> >");
                    vcv = Arrays.asList(condition.split(">"));
                    if(vcv.size() == 2) {
                        /* 对于不等式，必须先检查该字段是不是真的是数值型 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !(Long.valueOf(userData.getResourceDataMap().get(vcv.get(0))) > Long.valueOf(vcv.get(1)))
                            ));
                        } catch (Exception e){
                            /* 出现异常，说明该字段可能不是数值型或给出的条件值不是数值型，还有一种特殊情况，就是条件给出的字段不存在 */
                            System.out.println("异常 --> 该字段可能不是数值型或给出的条件值不是数值型");
                        }
                    }
                } else if(condition.contains("<")){
                    logger.debug("condition --> <");
                    vcv = Arrays.asList(condition.split("<"));
                    if(vcv.size() == 2) {
                        /* 对于不等式，必须先检查该字段是不是真的是数值型 */
                        try {
                            pageBean.getRecords().removeIf(userData -> (
                                    userData.getResourceDataMap().get(vcv.get(0)) == null ||
                                            !(Long.valueOf(userData.getResourceDataMap().get(vcv.get(0))) < Long.valueOf(vcv.get(1)))
                            ));
                        } catch (Exception e){
                            /* 出现异常，说明该字段可能不是数值型或给出的条件值不是数值型，还有一种特殊情况，就是条件给出的字段不存在 */
                            System.out.println("异常 --> 该字段可能不是数值型或给出的条件值不是数值型");
                        }
                    }
                }
            }
        }

        /* 数据变动，重新设置数据总量 */
//        System.out.println(beforeSize);
//        System.out.println(pageBean.getRecords().size());
        System.out.println("被过滤掉了" + (beforeSize - pageBean.getRecords().size()) + "条。");
        int nrChanges = beforeSize - pageBean.getRecords().size();
        if(nrChanges > 0){
            pageBean.setTotal(pageBean.getTotal() - nrChanges);
        }
        /* 返回结果 */
        return pageBean;
    }

    @Override
    @CacheEvict(key = "#id")
    public int remove(Long id) {
        DbIsUpdate = true;
        return userDataDao.deleteById(id);
    }

    @Override
    public int save(UserData record) {
        DbIsUpdate = true;
        return userDataDao.insert(record);
    }

    @Override
    @Cacheable(
            key = "#id",                               /* 缓存key使用用户的id */
            cacheManager = "jsonRedisCacheManager",     /* 使用json缓存管理器 */
            unless = "#result == null"                  /* 当没查到时，不缓存 */
    )
    public UserData findById(Long id) {
        return userDataDao.selectById(id);
    }

    @Override
    @CachePut(key = "#record.id")

    public int update(UserData record) {
        DbIsUpdate = true;
        return userDataDao.updateById(record);
    }

    @Override
    public int saveOrUpdate(UserData record) {
        DbIsUpdate = true;
        int rows = 0;
        // 根据是否有id决定是添加还是更新
        if(record.getId() == null){
            rows = save(record);
        } else {
            rows = update(record);
        }
        return rows;
    }
}
